package org.intelligentsia.temporal;

import java.util.Collections;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

import com.google.common.base.Preconditions;

/**
 * TemporalContainer is dedicated to a specific entity.
 * 
 * @author <a href="mailto:jguibert@intelligents-ia.com" >Jerome Guibert</a>
 * 
 */
public class TemporalContainer<T extends Temporal> {

	/**
	 * http://blog.xebia.fr/2011/02/17/java-collection-performance/
	 * 
	 * HashMap approach: O(n + k log k) average case, worst case could be much
	 * larger.
	 * 
	 * TreeMap approach: O(k + n log k) worst case.
	 * 
	 * where n = number of words in the text , k = number of distinct words in
	 * the text.
	 * 
	 * 
	 * In Java collections, the TreeMap is a sorted and navigable map that
	 * organizes elements in a self-balancing binary tree. It can help you solve
	 * problems like “Which element in this collection is the closest to this
	 * one?”
	 */
	private final SortedMap<TransactionTime, T>	entities;

	/**
	 * Factory of Temporal. Used to have no impact with objects parameters and
	 * returned.
	 */
	private final TemporalFactory<T>			factory;

	/**
	 * Build a new instance of TemporalContainer<T> with the specified factory.
	 * 
	 * @param factory
	 * @throws NullPointerException
	 *             if factory is null
	 */
	public TemporalContainer(final TemporalFactory<T> factory) throws NullPointerException {
		super();
		this.factory = Preconditions.checkNotNull(factory, "Temporal Factory cannot be null");
		// tree map is encapsulated for concurrent purpose.
		this.entities = Collections.synchronizedSortedMap(new TreeMap<TransactionTime, T>());
	}

	/**
	 * Add new temporal value.
	 * 
	 * @param value
	 */
	public void add(final T value) {
		Preconditions.checkState(entities.get(value.getTransactionTime()) == null,
				"container has TransactionTime overlap");
		entities.put(value.getTransactionTime(), value);
	}

	public void addAll(final List<T> temporals) {
		for (final T item : temporals) {
			add(item);
		}
	}

	/**
	 * @param startTime
	 * @return valid temporal for specified time.
	 */
	public T getTemporal(final long startTime) {
		final TransactionTime key = new TransactionTime(startTime);
		final T result = entities.get(key);
		return result != null ? factory.newInstance(result) : null;
	}

	/**
	 * Add a new temporal value.
	 * 
	 * @param value
	 * @param startTime
	 * @return
	 */
	public boolean update(final T value, final long startTime) {
		if (entities.isEmpty()) {
			add(value);
			return Boolean.TRUE;
		}
		final T original = getTemporal(startTime);
		// compute new transaction time
		final TransactionTime transactionTime = TransactionTime.intersect(original.getTransactionTime(), startTime);
		if (transactionTime != null) {
			value.setTransactionTime(transactionTime);
			// new value in order to be safe from parameter
			entities.put(transactionTime, factory.newInstance(value));
			return Boolean.TRUE;
		}
		return Boolean.FALSE;
	}

}
